package com.xcesys.extras.dataobject;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;

/**
 * Interface presents Tree node object.
 * 
 * @author Danne
 */
@SuppressWarnings("rawtypes")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TreeNode")
public abstract class TreeNode<T extends TreeNode> extends VersionableObject
		implements Comparable<T> {
	/** Property name of childNodes nodes. */
	public static final String F_CHILDREN = "childNodes";
	/** Property name of parentNode node. */
	public static final String F_PARENT = "parentNode";

	private static final long serialVersionUID = 4820808023643158373L;
	public static final String F_NAME = "name";
	public static final String F_DEPTH = "depth";
	public static final String F_LINEAGE = "lineage";
	public static final String F_SEQ = "seq";
	/**
	 * A list of childNodes nodes.
	 */
	private Set<T> childNodes;
	/**
	 * Depth value in tree.
	 */
	private Integer depth;

	/**
	 * Display name.
	 */
	private String displayName;

	/**
	 * Flag for deciding whether this node has childNodes node, default to true.
	 */
	// private boolean hasChildren = true;
	/**
	 * Flag indicates leaf node(has no children node).
	 */
	private Boolean leaf;
	/**
	 * Lineage representing tree path to this node.
	 */
	private String lineage;

	/**
	 * String value representing unique tree node name.
	 */
	private String name;

	/**
	 * Parent node id.
	 */
	private Long parentId;

	/**
	 * Parent node object.
	 */
	private T parentNode;

	/**
	 * Sortable sequence in tree node, if subclass should let it be set to max
	 * value automatically, so should set it to zero value before persisting it
	 */
	private Integer seq = Integer.valueOf(0);

	/**
	 * Adds a child node to this node.
	 * 
	 * @param child
	 *            child node.
	 */
	public synchronized void addChild(T child) {
		Set<T> childrenSet = getChildNodes();
		if (childrenSet == null) {
			childrenSet = new HashSet<T>();
			setChildNodes(childrenSet);
		}
		if (child.getParentNode() != null) {
			child.getParentNode().removeChild(child);
		}
		child.setParentId(this.getId());
		child.setParentNode(this);
		childrenSet.add(child);
	}

	//
	/**
	 * Adds a set of childNodes to this node.
	 * 
	 * @param childNodes
	 *            a set of childNodes node.
	 */
	public synchronized void addChildren(Collection<T> children) {
		for (T child : children) {
			addChild(child);
		}
	};

	@Override
	public int compareTo(T o) {

		Integer seq2 = o.getSeq();
		if (seq2 == null) {
			seq2 = Integer.valueOf(0);
		}
		return (this.seq == null ? Integer.valueOf(0) : this.seq)
				.compareTo(seq2);

	}

	/**
	 * Find a child node with given identifier.
	 * 
	 * @param id
	 *            id of node.
	 * @return
	 */
	public T findChild(Long id) {
		if (this.childNodes != null && !this.childNodes.isEmpty()) {
			for (T t : childNodes) {
				if (t.getId().equals(id)) {
					return t;
				}
			}
		}
		return null;
	}

	public Set<T> getChildNodes() {
		return childNodes;
	}

	public Integer getDepth() {
		return depth;
	}

	public String getDisplayName() {
		return displayName;
	}

	public String getLineage() {
		return lineage;
	}

	public String getName() {
		return name;
	}

	public Long getParentId() {
		return parentId;
	}

	public T getParentNode() {
		return parentNode;
	}

	public Integer getSeq() {
		return seq;
	}

	public boolean hasChild(T child) {
		if (this.childNodes != null) {
			boolean b = this.childNodes.contains(child);
			if (b) {
				return true;
			}
			for (T c : childNodes) {
				c.hasChild(child);
			}
		}
		return false;
	}

	/**
	 * Returns if this node has a parentNode node equals specified parentNode
	 * node.
	 * 
	 * @param node
	 *            parentNode not to be check.
	 * @return true if this node has a parentNode node equals to given
	 *         parentNode, otherwise false.
	 */
	public boolean hasParent(T node) {
		T parent = this.getParentNode();
		if (parent == null) {
			return false;
		}
		return parent.equals(node) || parent.hasParent(node);
	}

	/**
	 * Returns the boolean value represents whether this object has childNodes
	 * nodes or not.
	 * 
	 * @return true if has no childNodes, otherwise false.
	 */
	public Boolean isLeaf() {
		if (this.leaf == null) {
			this.leaf = (this.childNodes == null || this.childNodes.size() == 0);
		}
		return this.leaf;
	}

	/**
	 * Returns whether this node and given node is sibling element.
	 * 
	 * @param obj
	 *            another tree node.
	 * @return true if they are sibling, otherwise false.
	 */
	public boolean isSibling(T obj) {
		if (obj == null) {
			return false;
		}
		T parent = getParentNode();
		TreeNode anotherParent = obj.getParentNode();

		if (parent == null && anotherParent == null) {
			return true;
		} else if (parent != null && anotherParent != null
				&& parent.equals(anotherParent)) {
			return true;
		}
		return false;
	}

	/**
	 * Removes a child from this node childNodes set.
	 * 
	 * @param child
	 *            a child node.
	 */
	public synchronized void removeChild(TreeNode child) {
		Set<T> childrenSet = getChildNodes();
		if (childrenSet == null) {
			return;
		}
		child.setParentNode(null);
		childrenSet.remove(child);
	}

	/**
	 * Removes a set of childNodes node from this node childNodes set.
	 * 
	 * @param childNodes
	 *            a set of childNodes node.
	 */
	public synchronized void removeChildren(Collection<TreeNode> children) {
		for (TreeNode t : children) {
			removeChild(t);
		}
	}

	public void setChildNodes(Set<T> children) {
		Object oldValue = this.childNodes;
		this.childNodes = children;
		firePropertyChange("childNodes", oldValue, children);
	}

	/**
	 * Set a collection of childNodes nodes of this node.
	 * 
	 * @param childNodes
	 *            a collection of childNodes nodes.
	 */
	// public abstract void setChildren(Collection<? extends TreeNode>
	// childNodes);

	public void setDepth(Integer depth) {
		Object oldValue = this.depth;
		this.depth = depth;
		firePropertyChange("depth", oldValue, depth);
	}

	public void setDisplayName(String displayName) {
		Object oldValue = this.displayName;
		this.displayName = displayName;
		firePropertyChange("displayName", oldValue, displayName);
	}

	/**
	 * Set hasChildren property for determining this node has childNodes or not.
	 * 
	 * @param hasChildren
	 *            true to set hasChildren.
	 */
	// public void setHasChildren(boolean hasChildren) {
	// this.hasChildren = hasChildren;
	// }

	public void setLeaf(Boolean leaf) {
		Object oldValue = this.leaf;
		this.leaf = leaf;
		firePropertyChange("leaf", oldValue, leaf);
	}

	public void setLineage(String lineage) {
		Object oldValue = this.lineage;
		this.lineage = lineage;
		firePropertyChange("lineage", oldValue, lineage);
	}

	public void setName(String name) {
		Object oldValue = this.name;
		this.name = name;
		firePropertyChange("name", oldValue, name);
	}

	/**
	 * Set parentNode node of this node.
	 * 
	 * @param parentNode
	 *            parentNode node.
	 */
	// public abstract void setParent(TreeNode parentNode);

	public void setParentId(Long parentId) {
		Object oldValue = this.parentId;
		this.parentId = parentId;
		firePropertyChange("parentId", oldValue, parentId);
	}

	public void setParentNode(T parent) {
		Object oldValue = this.parentNode;
		this.parentNode = parent;
		firePropertyChange("parentNode", oldValue, parent);
	}

	public void setSeq(Integer seq) {
		Object oldValue = this.seq;
		this.seq = seq;
		firePropertyChange("seq", oldValue, seq);
	}

}
